
#include "nane/CodecSystem/Image/PNG/PNGImageDecoder.h"
#include "nane/core/assert.h"
#include "nane/core/IO.h"

#include <csetjmp>

namespace nane 
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static const size_t s_signatureBytesNum = 8;
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void s_readCallback( png_structp _png, png_bytep _buffer, png_size_t _bufferSize )
    {
        IInputRef* input = reinterpret_cast<IInputRef*>(png_get_io_ptr(_png));
        if( input != NULL )
        {
            input->Read(_buffer, _bufferSize);
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    PNGImageDecoder::PNGImageDecoder()
        : m_inputPtr( NullPtr )
        , m_width( 0 )
        , m_height( 0 )
        , m_format( EPixelFormat::Unknown )
        , m_png( NULL )
        , m_pngInfo( NULL )
        , m_rowSizeInBytes( 0 )
    {
        
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    PNGImageDecoder::~PNGImageDecoder()
    {
        NANE_ASSERT( m_inputPtr == NullPtr, "PNGImageDecoder must be shutdown before destruction" );
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool PNGImageDecoder::Initialize( IInputRef* _input )
    {
        NANE_ASSERT_RV( _input != NULL && m_inputPtr == NullPtr, "PNGImageDecoder must not be initialized and IInputRef must be valid", false );
        
        png_byte signature[s_signatureBytesNum] = { '\0' };
        if( IO<IOUnsafe>::Read(_input, &signature) != s_signatureBytesNum )
        {
            return false;
        }
        if( png_sig_cmp(signature, 0, s_signatureBytesNum) != 0 )
        {
            return false;
        }
        
        m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        if( m_png == NULL )
        {
            return false;
        }
        
        m_pngInfo = png_create_info_struct(m_png);
        if( m_pngInfo == NULL )
        {
            png_destroy_read_struct(&m_png, NULL, NULL);
            m_png = NULL;
            return false;
        }
        
        if( setjmp(png_jmpbuf(m_png)) )
        {
           png_destroy_read_struct(&m_png, &m_pngInfo, NULL);
           return false;
        }        
        
        png_set_read_fn(m_png, reinterpret_cast<void*>(_input), s_readCallback);
        png_set_sig_bytes(m_png, s_signatureBytesNum);
        
        png_read_info(m_png, m_pngInfo);
        int bit_depth, color_type;
        png_get_IHDR(m_png, m_pngInfo, &m_width, &m_height, &bit_depth, &color_type, NULL, NULL, NULL);
        
       // Expand paletted or RGB images with transparency to full alpha channels
       // so the data will be available as RGBA quartets.
       if( png_get_valid(m_png, m_pngInfo, PNG_INFO_tRNS) )
       {
          png_set_tRNS_to_alpha(m_png);
       }
        
       // Flip the RGB pixels to BGR (or RGBA to BGRA)
       if( color_type & PNG_COLOR_MASK_COLOR )
       {
          png_set_bgr(m_png);
       }
        
        if( (color_type & PNG_COLOR_TYPE_RGB_ALPHA) && (bit_depth == 8) )
        {
            m_format = EPixelFormat::A8R8G8B8;
            m_rowSizeInBytes = 4 * m_width;
        }
        
        png_read_update_info(m_png, m_pngInfo);
        m_inputPtr = InputPtr(_input);
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void PNGImageDecoder::Shutdown()
    {
        NANE_ASSERT_R( m_inputPtr != NullPtr, "PNGImageDecoder must be initialized" );
        
        png_read_end(m_png, m_pngInfo);
        png_destroy_read_struct(&m_png, &m_pngInfo, NULL);
        m_inputPtr = NullPtr;
        m_pngInfo = NULL;
        m_png = NULL;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 PNGImageDecoder::Read( byte* _buffer, uint32 _count )
    {
        NANE_ASSERT_RV( m_inputPtr != NullPtr, "PNGImageDecoder must be initialized", 0 );
        
        NANE_ASSERT_RV( _count >= m_rowSizeInBytes, "Buffer must be large enough to read whole row", 0 );
        
        if( setjmp(png_jmpbuf(m_png)) )
        {
           png_destroy_read_struct(&m_png, &m_pngInfo, NULL);
           return 0;
        }  
        
        png_read_rows(m_png, &_buffer, NULL, 1);
        
        return m_rowSizeInBytes;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 PNGImageDecoder::GetWidth()
    {
        NANE_ASSERT_RV( m_inputPtr != NullPtr, "PNGImageDecoder must be initialized", 0 );
        return m_width;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    uint32 PNGImageDecoder::GetHeight()
    {
        NANE_ASSERT_RV( m_inputPtr != NullPtr, "PNGImageDecoder must be initialized", 0 );
        return m_height;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    EPixelFormat::raw_type PNGImageDecoder::GetFormat()
    {
        NANE_ASSERT_RV( m_inputPtr != NullPtr, "PNGImageDecoder must be initialized", EPixelFormat::Unknown );
        return m_format.raw_value();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
